/**
 * License would add here
 */

#ifndef UTILS_JS_OBJECT_H
#define UTILS_JS_OBJECT_H

#include <csignal>
#include <cstdint>
#include <cstring>
#include <string>

#ifndef OHOS_PLATFORM
#include <node_api.h>
#else
#include <napi/native_api.h>
#endif

#include "js_string.h"
#include "js_value_base.h"
#include "js_value_wrapper.h"
#include "macros.h"

#define IS_OBJECT(type) VALUE_TYPE_CHECK(IsObject() || IsFunction(), "object", (type))

class JSObject : public JSValue {
public:
    JSObject(napi_env env)
        : JSValue(env)
    {
        napi_create_object(env_, &value_);
    }
    JSObject(napi_env env, napi_value value)
        : JSValue(env, value)
    {
        IS_OBJECT(type_);
    }
    JSObject(const JSValue& value)
        : JSValue(value)
    {
        IS_OBJECT(type_);
    };

protected:
    template<typename Object, typename Key>
    class PropertyLValue : public JSValue {
    public:
        PropertyLValue(const Object& object, const Key& key);
        PropertyLValue& operator=(const JSValue& value);
    private:
        void UpdateResult();
        napi_env env_;
        napi_value object_;
        Key key_;
    friend class JSObject;
    };

public:
    const PropertyLValue<JSObject, JSValue> Get(const JSValue& key) const;
    const PropertyLValue<JSObject, JSValue> Get(const napi_value& key) const;
    const PropertyLValue<JSObject, JSValue> Get(const std::string& key) const;
    const PropertyLValue<JSObject, JSValue> Get(const char* key) const;

    PropertyLValue<JSObject, JSValue> operator[](const JSValue& key) const { return Get(key); }
    PropertyLValue<JSObject, JSValue> operator[](const napi_value& key) const { return Get(key); }
    PropertyLValue<JSObject, JSValue> operator[](const std::string& key) const { return Get(key); }
    PropertyLValue<JSObject, JSValue> operator[](const char* key) const { return Get(key); }

    inline void Set(const JSValue& key, const JSValue& value)
    {
        NAPI_STATUS_CALL(env_, napi_set_property(env_, value_, key, value));
    }
    inline void Set(const napi_value& key, const napi_value& value)
    {
        NAPI_STATUS_CALL(env_, napi_set_property(env_, value_, key, value));
    }
    inline void Set(const std::string& key, const JSValue& value)
    {
        NAPI_STATUS_CALL(env_, napi_set_named_property(env_, value_, key.c_str(), value));
    }
    inline void Set(const std::string& key, const napi_value& value)
    {
        NAPI_STATUS_CALL(env_, napi_set_named_property(env_, value_, key.c_str(), value));
    }

    bool Has(const JSValue& key)
    {
        bool result;
        NAPI_STATUS_CALL(env_, napi_has_property(env_, value_, key, &result));
        return result;
    }
    bool Has(const napi_value& key)
    {
        bool result;
        NAPI_STATUS_CALL(env_, napi_has_property(env_, value_, key, &result));
        return result;
    }
    bool Has(const std::string& key)
    {
        bool result;
        NAPI_STATUS_CALL(env_, napi_has_named_property(env_, value_, key.c_str(), &result));
        return result;
    }

    bool HasOwnProperty(const JSValue& key)
    {
        bool result;
        NAPI_STATUS_CALL(env_, napi_has_own_property(env_, value_, key, &result));
        return result;
    }
    bool HasOwnProperty(const napi_value& key)
    {
        bool result;
        NAPI_STATUS_CALL(env_, napi_has_own_property(env_, value_, key, &result));
        return result;
    }
    bool HasOwnProperty(const std::string& key)
    {
        return HasOwnProperty(JSString(env_, key));
    }

    bool Delete(const JSValue& key){
        bool result = false;
        NAPI_STATUS_CALL(env_, napi_delete_property(env_, value_, key, &result));
        return result;
    }
    bool Delete(const napi_value& key){
        bool result = false;
        NAPI_STATUS_CALL(env_, napi_delete_property(env_, value_, key, &result));
        return result;
    }
    bool Delete(const std::string& key){
        return Delete(JSString(env_, key));
    }

    bool Instanceof(const JSValue& constructor)
    {
        bool result = false;
        napi_instanceof(env_, value_, constructor, &result);
        return result;
    }
    bool Instanceof(const napi_value& constructor)
    {
        bool result = false;
        napi_instanceof(env_, value_, constructor, &result);
        return result;
    }

    void* Unwrap()
    {
        void* result = nullptr;
        NAPI_STATUS_CALL(env_, napi_unwrap(env_, value_, &result));
        if (result != nullptr) {
            return reinterpret_cast<JSValueWrapper*>(result)->GetData();
        } else {
            return result;
        }
    }

protected:
    JSObject(const napi_env& env, bool empty)
        : JSValue(env)
    {
        if (!empty) {
            napi_create_object(env_, &value_);
            UpdateType();
        }
    }

private:
friend class PropertyLValue<JSObject, JSValue>;
};

inline JSObject JSValue::GetGlobal()
{
    return GetGlobal(env_);
}

inline JSObject JSValue::GetGlobal(const napi_env& env)
{
    JSValue result(env);
    NAPI_STATUS_CALL(env, napi_get_global(env, &result));
    result.UpdateType();
    return result.As<JSObject>();
}

inline JSObject JSValue::ToObject() const
{
    JSValue object(env_);
    NAPI_STATUS_CALL(env_, napi_coerce_to_object(env_, value_, &object));
    object.UpdateType();
    return object.As<JSObject>();
}

#undef IS_OBJECT
#endif /* UTILS_JS_OBJECT_H */
